//
// itertool - a polynormial and type-safe iterator framework in C++.
//
// Copyright (c) 2021-2025 Feng Cong
//
// Distributed under the MIT Software License
// See accompanying file LICENSE.txt or copy at
// https://opensource.org/licenses/MIT

#pragma once

#include <itertool/itertool_fwd.h>

namespace itertool {
namespace detail {

template <typename T, typename = void>
struct CycleIterableRequires : std::false_type {};

template <typename T>
struct CycleIterableRequires<
    T, std::void_t<std::vector<T>,
                   std::enable_if_t<std::is_copy_constructible_v<T>>>>
    : std::true_type {};

template <typename T>
class ResetingCycleIterable : InfiniteIterableBase<T> {
 public:
  using ItemType = T;

  explicit ResetingCycleIterable(itertool::Iterator<T> it) : it_(it) {
    assert(it.IsResetable());
    assert(it.IsFinite());
  }

  ItemType GetNow() override { return it_.GetNow(); }

  bool IsResetable() override { return true; }

  void Reset() override { it_.Reset(); }

  void Advance() override {
    it_.Advance();
    if (!it_.HasNext()) {
      it_.Reset();
      assert(it_.HasNext());
    }
  }

 private:
  Iterator<T> it_;
};

template <typename T>
class CachingCycleIterable : public InfiniteIterableBase<T> {
  static_assert(CycleIterableRequires<T>::value, "");

 public:
  using ItemType = T;

  template <typename InputIter>
  CachingCycleIterable(InputIter first, InputIter last) : data_(first, last) {}

  void Advance() override {
    ++now_;
    if (now_ >= data_.size()) {
      now_ = 0;
    }
  }

  bool IsResetable() override { return true; }

  void Reset() override { now_ = 0; }

  ItemType GetNow() override { return data_[now_]; }

 private:
  size_t now_ = 0;
  const std::vector<T> data_;
};

}  // namespace detail

template <typename T>
Iterator<T> Cycle(Iterator<T> it) {
  assert(it.IsFinite() && "Cycle requires a finite iterator");
  using Reseting = detail::ResetingCycleIterable<T>;
  using Caching = detail::CachingCycleIterable<T>;
  return it.IsResetable() ? MakeIterator<Reseting>(it)
                          : MakeIterator<Caching>(it);
}

template <typename T>
Iterator<T> Cycle(std::initializer_list<T> list) {
  return Cycle(list.begin(), list.end());
}

template <
    typename InputIter,
    typename _ItemType = typename std::iterator_traits<InputIter>::value_type>
Iterator<_ItemType> Cycle(InputIter first, InputIter last) {
  return MakeIterator<detail::CachingCycleIterable<_ItemType>>(first, last);
}

}  // namespace itertool
